/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.navid.trafalgar.games;

import com.jme3.app.Application;
import com.jme3.app.StatsAppState;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.font.BitmapFont;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.Filter;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.system.AppSettings;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.navid.trafalgar.camera.ChaseCamera;
import com.navid.trafalgar.camera.TargetCamera;
import com.navid.trafalgar.definition.GameDefinition;
import com.navid.trafalgar.model.AMillestoneModel;
import com.navid.trafalgar.model.IContext;
import com.navid.trafalgar.model.GameModel;
import com.navid.trafalgar.model.IModelBuilder;
import com.navid.trafalgar.model.AShipModel;
import com.navid.trafalgar.model.ModelBuilderFactory;
import com.navid.trafalgar.util.FormatUtils;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 *
 * @author alberto
 */
public class Millestone2 extends AbstractAppState implements ScreenController {

    private Nifty nifty;
    private Application app;
    private Screen screen;
    //Constructor
    AppSettings settings;
    //ModelBuilder
    IModelBuilder modelBuilder;
    //Managers
    AppStateManager stateManager;
    BulletAppState bulletAppState;
    AssetManager assetManager;
    RenderManager renderManager;
    InputManager inputManager;
    //Scene
    Node millestoneScene;
    ViewPort viewPort;
    ViewPort guiViewPort;
    Camera camera;
    //Cameras
    ChaseCamera chaseCamControl;
    TargetCamera targetCamControl;
    FlyByCamera flyCamControl;
    IContext iContext;
    TerrainQuad terrain;
    //ShipModel ship;
    Map<String, AMillestoneModel> millestones;
    AShipModel ship;
    //Gui
    protected Node guiNode;
    protected BitmapFont guiFont;
    GameModel gameModel;
    private boolean inplay;

    Millestone2(AppSettings settings) {
        this.settings = settings;
        this.setEnabled(false);
    }

    /** Nifty GUI ScreenControl methods */
    public void bind(Nifty nifty, Screen screen) {
        this.nifty = nifty;
        this.screen = screen;
    }

    private void loadGameFromDefinitionFile() throws FileNotFoundException, IOException {

        GameDefinition game = (GameDefinition) assetManager.loadAsset("Games/Millestone2.json");

        gameModel = modelBuilder.buildGameModel(game);

        iContext = gameModel.getiContext();
        millestoneScene.attachChild(iContext.getWind().getGeometry());

        Map<String, AShipModel> ships = gameModel.getShips();
        for (AShipModel currentShip : ships.values()) {
            ship = currentShip;
            ship.registerInput(inputManager);
        }

        millestones = gameModel.getMillestones();

        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        for (Filter currentFilter : gameModel.getFpp()) {
            fpp.addFilter(currentFilter);
        }

        viewPort.addProcessor(fpp);
        millestoneScene.attachChild(gameModel.getGameNode());
    }

    public void onStartScreen() {

        app.enqueue(new Callable<Spatial>() {

            public Spatial call() throws Exception {

                screen.findElementByName("retryLayer").setVisible(false);
                hideMenu();
        
                bulletAppState = new BulletAppState();
                stateManager.attach(bulletAppState);

                time = 0.0f;
                camera = new Camera(settings.getWidth(), settings.getHeight());
                camera.setFrustumPerspective(45f, (float) camera.getWidth() / camera.getHeight(), 1f, 1000f);
                camera.setFrustumFar(10000);

                viewPort = renderManager.createMainView("millestone1", camera);
                viewPort.setClearFlags(true, true, true);
                viewPort.setBackgroundColor(ColorRGBA.Blue);
                viewPort.attachScene(millestoneScene);

                loadGameFromDefinitionFile();

                // Create a flying cam
                flyCamControl = new FlyByCamera(camera);

                //Create a target cam
                targetCamControl = new TargetCamera(camera, ship, millestones.get("mil1"));

                // Enable a chasing cam
                chaseCamControl = new ChaseCamera(camera, ship, inputManager);
                chaseCamControl.setSmoothMotion(true);
                chaseCamControl.setEnabled(true);

                initKeys(); // load my custom keybinding

                Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
                guiViewPort = renderManager.createPostView("Gui Default", guiCam);
                guiViewPort.setClearFlags(false, false, false);

                guiNode = new Node("Gui Node");
                guiNode.setQueueBucket(Bucket.Gui);
                guiNode.setCullHint(CullHint.Never);
                guiViewPort.attachScene(guiNode);

                guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

                stateManager.attach(new StatsAppState(guiNode, guiFont));

                setEnabled(true);

                inplay = true;

                return null;

            }
        });

    }

    public void onEndScreen() {

        app.enqueue(new Callable<Spatial>() {

            public Spatial call() throws Exception {

                renderManager.removeMainView(viewPort);
                renderManager.removeMainView(guiViewPort);
                viewPort.detachScene(millestoneScene);
                guiViewPort.detachScene(guiNode);
                millestoneScene.detachAllChildren();
                setEnabled(false);

                removeKeys();

                return null;
            }
        });
    }

    public void goTo(String nextScreen) {
        nifty.gotoScreen(nextScreen);  // switch to another screen
    }

    /** jME3 AppState methods */
    @Override
    public void initialize(AppStateManager stateManager, Application app) {

        this.app = app;
        this.stateManager = stateManager;


        assetManager = app.getAssetManager();
        renderManager = app.getRenderManager();
        inputManager = app.getInputManager();

        millestoneScene = new Node("Millestone Scene");

        modelBuilder = ModelBuilderFactory.getModelBuilder(settings, assetManager);
    }
    String[] mappings = {"Cam1", "Cam2", "Cam3", "Menu"};

    /** Custom Keybinding: Map named actions to inputs. */
    private void initKeys() {
        // You can map one or several inputs to one named action
        inputManager.addMapping("Cam1", new KeyTrigger(KeyInput.KEY_1));
        inputManager.addMapping("Cam2", new KeyTrigger(KeyInput.KEY_2));
        inputManager.addMapping("Cam3", new KeyTrigger(KeyInput.KEY_3));
        inputManager.addMapping("Menu", new KeyTrigger(KeyInput.KEY_ESCAPE));

        // Add the names to the action listener.
        inputManager.addListener(actionListener, mappings);
    }

    private void removeKeys() {
        for (String currentMapping : mappings) {
            inputManager.deleteMapping(currentMapping);
        }

        inputManager.removeListener(actionListener);
    }
    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean isPressed, float tpf) {
            if (name.equals("Cam1") && isPressed) {
                setCamera1();
            }
            if (name.equals("Cam2") && isPressed) {
                setCamera2();
            }
            if (name.equals("Cam3") && isPressed) {
                setCamera3();
            }
            if (name.equals("Menu") && isPressed) {
                toggleMenu();
            }
        }
    };
    //This part is to emulate tides, slightly varrying the height of the water plane
    private float time = 0.0f;

    private void setCamera1() {
        if (chaseCamControl.isEnabled()) {
            chaseCamControl.setEnabled(false);
        }

        if (targetCamControl.isEnabled()) {
            targetCamControl.setEnabled(false);
        }

        flyCamControl.registerWithInput(inputManager);
        flyCamControl.setMoveSpeed(100);
        flyCamControl.setEnabled(true);
    }

    private void setCamera2() {
        if (flyCamControl.isEnabled()) {
            flyCamControl.setEnabled(false);
        }

        if (targetCamControl.isEnabled()) {
            targetCamControl.setEnabled(false);
        }

        chaseCamControl.registerWithInput(inputManager);
        chaseCamControl.setEnabled(true);
    }

    private void setCamera3() {
        if (flyCamControl.isEnabled()) {
            flyCamControl.setEnabled(false);
        }

        if (chaseCamControl.isEnabled()) {
            chaseCamControl.setEnabled(false);
        }

        targetCamControl.registerWithInput(inputManager);
        targetCamControl.setEnabled(true);
    }

    public void clickCamera1() {
        setCamera1();
    }

    public void clickCamera2() {
        setCamera2();
    }

    public void clickCamera3() {
        setCamera3();
    }

    public void showMenu() {
        screen.findElementByName("menuLayer").setVisible(true);
        menuStatus = true;
    }
    
    public void hideMenu() {
        screen.findElementByName("menuLayer").setVisible(false);
        menuStatus = false;
    }
    
    private boolean menuStatus = false;
    
    public void toggleMenu() {
        if (menuStatus){
            hideMenu();
        } else {
            showMenu();
        }   
    }

    private void finished() {
        screen.findElementByName("retryLayer").setVisible(true);
    }

    @Override
    public void update(float tpf) {

        if (inplay) {

            time += tpf;

            //updating position
            ship.update(tpf);

            //reposition of wind arrow
            Vector3f vectShip = ship.getLocalTranslation();
            iContext.getWind().getGeometry().setLocalTranslation(vectShip.x, vectShip.y + 2, vectShip.z);


            //Checking if all millestones passed
            boolean anyDidntFinish = true;
            boolean targetCameraAsigned = false;

            for (AMillestoneModel currentMillestone : millestones.values()) {
                currentMillestone.updateState(ship);
                boolean currentMillestoneState = currentMillestone.getState();
                anyDidntFinish = anyDidntFinish && currentMillestoneState;
                if (!currentMillestoneState && !targetCameraAsigned) {
                    targetCameraAsigned = true;
                    targetCamControl.setTarget(currentMillestone);
                }

            }

            if (!anyDidntFinish) {
                screen.findElementByName("mesh_stats").getRenderer(TextRenderer.class).setText("Time: " + time);
            } else {
                finished();
            }

        }

        screen.findElementByName("speed_stats").getRenderer(TextRenderer.class).setText("speed: " + FormatUtils.formatFloatX_XXX(ship.getSpeed()));
        screen.findElementByName("wind_stats").getRenderer(TextRenderer.class).setText("wind orientation: " + FormatUtils.formatFloatX_XXX(iContext.getWind().getWind().getAngle()));
        screen.findElementByName("orientation_stats").getRenderer(TextRenderer.class).setText("ship orientation: " + FormatUtils.formatFloatX_XXX(ship.getOrientation()));



        //Little trick
        millestoneScene.updateLogicalState(tpf);
        guiNode.updateLogicalState(tpf);
        millestoneScene.updateGeometricState();
        guiNode.updateGeometricState();

    }
}